import merge = require('deepmerge');

export enum ArrayMergeMode {
  MERGE   = 'merge',
  REPLACE = 'replace',
  COMBINE = 'combine'
}

type MergeOptions = {
  arrayMode: ArrayMergeMode,
  arrayMerge?: (target: any, source: any, options: MergeInsideOptions) => any
}

type MergeInsideOptions = MergeOptions & {
  isMergeableObject: (item: any) => boolean,
  cloneUnlessOtherwiseSpecified: (item: any, opts: MergeOptions) => any
}

/**
 * arrayMerge 的 callback
 *
 * @param target
 * @param source
 * @param options
 */
const arrayMerge = (target: any, source: any, options: MergeInsideOptions) => {
  const destination = target.slice();
  const { arrayMode } = options;

  if (arrayMode === ArrayMergeMode.REPLACE) {
    return source;
  }

  if (arrayMode === ArrayMergeMode.COMBINE) {
    source.forEach((item: any, index: number) => {
      if (typeof destination[index] === 'undefined') {
        destination[index] = options.cloneUnlessOtherwiseSpecified(item, options);
      } else if (options.isMergeableObject(item)) {
        destination[index] = merge(target[index], item, options);
      } else if (target.indexOf(item) === -1) {
        destination.push(item);
      }
    });
  } else {
    source.forEach((item: any, index: number) => {
      if (typeof destination[index] === 'undefined') {
        destination[index] = options.cloneUnlessOtherwiseSpecified(item, options);
      } else {
        if (options.isMergeableObject(item))
          destination[index] = merge(target[index], item, options);
        else
          destination[index] = item;
      }
    });
  }
  return destination;
};

/**
 * 生成 merge 的选项
 *
 * @param options
 */
const mergeOptions = (options?: MergeOptions): MergeOptions => {
  if (typeof options !== 'object') {
    options = {} as MergeOptions;
  }

  let { arrayMode } = options;
  if (arrayMode == null) {
    arrayMode = ArrayMergeMode.MERGE;
  }
  options.arrayMode = arrayMode;

  if (typeof options.arrayMerge !== 'function') {
    options.arrayMerge = arrayMerge;
  }

  return options;
};

/**
 * 将多个要合并的对象合并为一个对象
 *
 * @param objects
 * @param options
 */
export const deepMerge = <T>(objects: T[], options?: MergeOptions): T => {
  return merge.all(objects, mergeOptions(options));
};
